Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
117 / 117
100.00% covered (success)
100.00%
7 / 7
CRAP
100.00% covered (success)
100.00%
1 / 1
SpanBuilder
100.00% covered (success)
100.00%
117 / 117
100.00% covered (success)
100.00%
7 / 7
18
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 buildSpanRecords
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
6
 buildSpanRecord
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
2
 buildSpanRecordBase
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
5
 buildQuerySpanAdditionalData
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
1
 buildRedisCommandSpanAdditionalData
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
1
 buildHttpSpanAdditionalData
100.00% covered (success)
100.00%
31 / 31
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3namespace Nivseb\LaraMonitor\Elastic\Builder;
4
5use Carbon\CarbonInterface;
6use Illuminate\Support\Collection;
7use Nivseb\LaraMonitor\Contracts\Elastic\ElasticFormaterContract;
8use Nivseb\LaraMonitor\Contracts\Elastic\SpanBuilderContract;
9use Nivseb\LaraMonitor\Struct\Spans\AbstractSpan;
10use Nivseb\LaraMonitor\Struct\Spans\HttpSpan;
11use Nivseb\LaraMonitor\Struct\Spans\QuerySpan;
12use Nivseb\LaraMonitor\Struct\Spans\RedisCommandSpan;
13use Nivseb\LaraMonitor\Struct\Transactions\AbstractTransaction;
14
15class SpanBuilder implements SpanBuilderContract
16{
17    public function __construct(
18        protected ElasticFormaterContract $formater
19    ) {}
20
21    /**
22     * @param Collection<array-key, AbstractSpan> $spans
23     */
24    public function buildSpanRecords(AbstractTransaction $transaction, Collection $spans): array
25    {
26        if (!$transaction->startAt || !$transaction->finishAt) {
27            return [];
28        }
29        $spanRecords = [];
30        foreach ($spans as $span) {
31            $spanRecord = $this->buildSpanRecord($span, $transaction->startAt);
32            if (!$spanRecord) {
33                continue;
34            }
35            $spanRecords[] = ['span' => $spanRecord];
36        }
37
38        $spanRecords = array_filter($spanRecords);
39        if (!$spanRecords) {
40            return [];
41        }
42
43        return $spanRecords;
44    }
45
46    protected function buildSpanRecord(AbstractSpan $span, CarbonInterface $transactionStart): ?array
47    {
48        $spanRecord = $this->buildSpanRecordBase($span, $transactionStart);
49        if (!$spanRecord) {
50            return null;
51        }
52
53        return [
54            ...$spanRecord,
55            ...match (true) {
56                $span instanceof QuerySpan        => $this->buildQuerySpanAdditionalData($span),
57                $span instanceof RedisCommandSpan => $this->buildRedisCommandSpanAdditionalData($span),
58                $span instanceof HttpSpan         => $this->buildHttpSpanAdditionalData($span),
59                default                           => [],
60            },
61        ];
62    }
63
64    protected function buildSpanRecordBase(AbstractSpan $span, CarbonInterface $transactionStart): ?array
65    {
66        $timestamp = $this->formater->getTimestamp($span->startAt);
67        $typeData  = $this->formater->getSpanTypeData($span);
68        $duration  = $this->formater->calcDuration($span->startAt, $span->finishAt);
69        $start     = $this->formater->calcDuration($transactionStart, $span->startAt);
70        if (!$typeData || !$timestamp || $duration === null || $start === null) {
71            return null;
72        }
73
74        return [
75            'id'          => $span->getId(),
76            'parent_id'   => $span->parentEvent->getId(),
77            'trace_id'    => $span->getTraceId(),
78            'name'        => $span->getName(),
79            'timestamp'   => $timestamp,
80            'duration'    => $duration,
81            'start'       => $start,
82            'type'        => $typeData->type,
83            'subtype'     => $typeData->subType,
84            'action'      => $typeData->action,
85            'sync'        => true,
86            'outcome'     => $this->formater->getOutcome($span),
87            'sample_rate' => 1,
88        ];
89    }
90
91    protected function buildQuerySpanAdditionalData(QuerySpan $span): array
92    {
93        return [
94            'context' => [
95                'db' => [
96                    'instance'  => $span->host,
97                    'statement' => $span->sqlStatement,
98                    'type'      => 'sql',
99                ],
100                'destination' => [
101                    'service' => [
102                        'resource' => $span->databaseType.'/'.$span->host,
103                    ],
104                ],
105            ],
106            'service' => [
107                'target' => [
108                    'name' => $span->host,
109                    'type' => $span->databaseType,
110                ],
111            ],
112        ];
113    }
114
115    protected function buildRedisCommandSpanAdditionalData(RedisCommandSpan $span): array
116    {
117        return [
118            'context' => [
119                'db' => [
120                    'instance'  => $span->host,
121                    'statement' => $span->statement,
122                    'type'      => 'redis',
123                ],
124                'destination' => [
125                    'service' => [
126                        'resource' => 'redis/'.$span->host,
127                    ],
128                ],
129            ],
130            'service' => [
131                'target' => [
132                    'name' => $span->host,
133                    'type' => 'redis',
134                ],
135            ],
136        ];
137    }
138
139    protected function buildHttpSpanAdditionalData(HttpSpan $span): array
140    {
141        return [
142            'context' => [
143                'http' => [
144                    'method' => $span->method,
145                    'url'    => $span->scheme
146                        .'://'.$span->host.(!str_contains($span->host, '.') ? '.localhost' : ''),
147                    'response' => [
148                        'headers'           => null,
149                        'status_code'       => $span->responseCode,
150                        'transfer_size'     => null,
151                        'decoded_body_size' => null,
152                        'encoded_body_size' => null,
153                    ],
154                    'destination' => [
155                        'address' => $span->host,
156                        'port'    => $span->port,
157                        'service' => [
158                            'name'     => '//'.$span->host,
159                            'resource' => $span->host,
160                            'type'     => 'external',
161                        ],
162                    ],
163                ],
164            ],
165            'service' => [
166                'target' => [
167                    'name' => $span->host,
168                    'type' => 'http',
169                ],
170            ],
171        ];
172    }
173}